package ks.client.game;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Properties;

import ks.client.interfaces.IGameInterface;

/**
 * Represents class of information for each game.
 * <p>
 * When starting a game, GameManager properly calls the {@link #start(GameFrame)}
 * and {@link #initialize()} methods of this base class (and the proper 
 * logic is encoded in the subclasses.)
 * <p>
 * Externally, as responses are received from the server, your teams
 * will have to process these responses and determine how to update
 * the internal game state. To do this you will need to invoke 
 * a number of methods as defined by this abstract class.
 * <ol>
 * <li>{@link #activateTurn(String)} is to be called when it is the 
 * player's turn to make moves. In solitaire, this is always the case
 * so make sure you call at the start of each game. In turn-based games,
 * you should only invoke this method when you are told that it is the
 * player's turn (check the gameResponse method).
 * <li>{@link #makeTurn(String, String)} is to be called when the
 * current client is quiescent and the appropriate player on the table
 * makes a move.
 * <li>{@link #updateScores(Properties, Properties)} is to be called when
 * the current client is playing a solitiare game and the server has 
 * emitted a gameResponse message in response to changing player scores
 * <li>{@link #skipTurn(String)} is to be called when in a multi-player
 * game a player (whether current or not) skips a turn (whether by
 * choice or by timer expiration).
 * <li>{@link #requestLeave(String)} is to be called when any player
 * other than the current one decides to leave a table/game. 
 * <p>
 * 
 * 
 * @author George Heineman
 */
public abstract class GameInformation {

	/** Seed to use. */
	final public int seed;
	
	/** Game information. */
	final public String game;
	
	/** Default seed to use. */
	public static final int defaultSeed = 110;
	
	/** Player's id. */
	protected String player;
	
	/**
	 * Player ids on the table. Keys are the IDs while values are the real player names.  
	 */
	protected Properties players = new Properties();
	
	/** Order of players on the table. */
	protected ArrayList<String> order;
	
	/** Callback object. */
	protected IGameInterface callback;
	
	/** Whether game is in its initial state. */
	boolean initialState = true;
		
	/**
	 * Handles game-generic options.
	 * <p>
	 * Note that this constructor is protected so it can be accessed
	 * by subclasses.
	 * 
	 * If 'seed' is missing, then the {@link #defaultSeed} value is used.
	 * 
	 * @param options contains 'game' and 'seed' attributes.
	 * @exception     if game or seed attribute is missing or invalid
	 */
	protected GameInformation(Properties options) throws IllegalArgumentException {
		
		game = options.getProperty("game");
		String seedS = options.getProperty("seed");
		
		if (seedS == null) {
			seed = defaultSeed;
		} else {
			// interpret as Integer, if possible.
			try {
				seed = Integer.valueOf(seedS);
			} catch (Exception e) {
				throw new IllegalArgumentException("invalid seed value:" + seedS);
			}
		}
	}

	/** Game may need to know who the player is for display purposes. */
	public void setCurrentUser(String me) {
		this.player = me;
	}
	
	/** Retrieve owner for this game. */
	public String getCurrentUser() {
		return player;
	}

	/** To ensure consistency, use this seed when requesting a new random number. */
	public int getSeed() {
		return seed;
	}
	
	/** Start the game. */
	public abstract boolean start(GameFrame frame);

	/** Return the graphical container within which game-specific controls exist. */
	public abstract java.awt.Component getGameControls();
	
	/** Return the graphical container within which game plays. */
	public abstract java.awt.Component getGameContainer();

	/** Initialize the game to start. */
	public void initialize() {
		initialState = true;
	}

	/** Do what it takes to stop the game. */
	public abstract void stop();

	/**
	 * Set the players for the table as defined within the playerIDs 
	 * properties object.
	 * <p>
	 * If there were existing players on the table, then this invocation
	 * replaces those (which means you can either add or remove names by
	 * calling this method multiple times as you see fit). 
	 * 
	 * The keys for the Properties object contain the unique Player IDs
	 * used on the system and the value (if it exists) represents the 
	 * real name associated with that player.
	 * <p>
	 * @param playerOrder   order of players on table
	 * @param playerIDs     pass in <Key=ID, Value=RealName>
	 */
	public void setPlayers(ArrayList<String> playerOrder, Properties playerIDs) {
		Properties newP = new Properties();
		for (Enumeration<?> en = playerIDs.keys(); en.hasMoreElements(); ) {
			String id = (String) en.nextElement();
			String val = playerIDs.getProperty(id);
			newP.setProperty(id, val);
		}
		
		players = newP;
		order = new ArrayList<String>();
		for (String s: playerOrder) {
			order.add(s);
		}
	}
	
	/** 
	 * Get full name for player "Real Name [id]". 
	 * 
	 * @param playerID
	 */
	public String fullName(String playerID) {
		String realname = (String) players.get(playerID);
		String fullName = "[" + playerID + "]";
		if (realname == null || realname.equals ("")) { return fullName; }
		
		// append the real name.
		return fullName + " " + realname;
	}

	/**
	 * Specialized game logic also needs access to callback object.
	 * 
	 * @param callback
	 */
	public void setGameInterface(IGameInterface callback) {
		this.callback = callback;		
	}

	/**
	 * Ensure that the current player gets the chance to play.
	 * <p>
	 * This is redundant in solitaire-based games but it is essential
	 * in turn-based games.
	 * <p>
	 * Not sure how much error handling is possible here.
	 * <p>
	 * Overriding method must properly return true or false.
	 * 
	 * @param playerID   player whose turn has come up
	 * @return <code>true</code> if successfully changed turn; <code>false</code> on problem.
	 */
	public boolean activateTurn(String playerID) {
		initialState = false;
		return false;
	}

	/**
	 * Ensure that the given player's move is executed on the game
	 * state. 
	 * <p>
	 * In turn-based games, the client is paused waiting for external
	 * clients to complete their moves, as determined by the incoming
	 * string. The underlying game class knows how to process this 
	 * incoming data.
	 * <p>
	 * There is no need to pass in 'player score' information because the
	 * underlying turn-based game will simply "play the move" and in doing
	 * so compute the last player's score (and posssibly affecting other
	 * player scores as well).
	 * <p>
	 * Not sure how much error handling is possible here.
	 * <p>
	 * Overriding method must properly return true or false.
	 * 
	 * @param playerID    player whose turn has been made
	 * @param moveString  move that was executed (opaque to server and client code).
	 * 
	 * @return <code>true</code> if successfully played turn; <code>false</code> on problem.
	 */
	public boolean makeTurn(String playerID, String moveString) {
		initialState = false;
		return false;
	}

	/**
	 * Ensure that the game's player scores are properly updated to reflect
	 * the changing scores.
	 * <p>
	 * This is to be invoked when the GameResponse is received from the server
	 * and you have to update the game with information from other players on 
	 * the same table.
	 *
	 * Check out sample gameResponse message for info on the updatedGame value.
	 *
	 * @param updatedScores   represents Properties where key=playerID and value=score
	 * @param updatedGame     represents Properties where key=playerID and value=game-specific information
	 * @return <code>true</code> if successfully updated scores; <code>false</code> on problem.
	 */
	public abstract boolean updateScores(Properties updatedScores, Properties updatedGame);
	
	/**
	 * Ensure that the given player's move is identified as a skip.
	 * <p>
	 * Overriding method must properly return true or false.
	 * 
	 * @return <code>true</code> if successfully skipped turn; <code>false</code> on problem.
	 */
	public boolean skipTurn(String playerID) {
		initialState = false;	
		return false;
	}

	/**
	 * Ensure that the given player is eliminated from the game.
	 *
	 * @return <code>true</code> if successfully removed; <code>false</code> on problem.
	 */
	public boolean requestLeave(String playerID) {
		if (!order.contains(playerID)) { return false; }
		
		// remove player from consideration.
		order.remove(playerID);
		players.remove(playerID);
		
		return true;
	}


	/**
	 * Determine if the game is in its pristine newly started state.
	 * 
	 * @return <code>true</code> if no move has yet to be made on the board; <code>false</code> otherwise.
	 */
	public boolean isFirstTurn() {
		return initialState;
	}

	/**
	 * Glorified hack to be able to declare that the opening move (which was a skip) has 
	 * advanced the board state so it no longer is pristine. SHEESH.
	 */
	public void hackFirstSkipReceived() {
		initialState = false;
	}
	
}
